Copyright 2003-2007 Chris Mallett (support@autohotkey.com)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "stdafx.h" // pre-compiled headers
#include "keyboard_mouse.h"
#include "globaldata.h" // for g.KeyDelay
#include "application.h" // for MsgSleep()
#include "util.h" // for strlicmp()
#include "window.h" // for IsWindowHung()
// Added for v1.0.25. Search on sPrevEventType for more comments:
static KeyEventTypes sPrevEventType;
static vk_type sPrevVK = 0;
// For v1.0.25, the below is static to track it in between sends, so that the below will continue
// to work:
// Send {LWinDown}
// Send {LWinUp} ; Should still open the Start Menu even though it's a separate Send.
static vk_type sPrevEventModifierDown = 0;
static modLR_type sModifiersLR_persistent = 0; // Tracks this script's own lifetime/persistent modifiers (the ones it caused to be persistent and thus is responsible for tracking).
// v1.0.44.03: Below supports multiple keyboard layouts better by having script adapt to active window's layout.
#define MAX_CACHED_LAYOUTS 10 // Hard to imagine anyone using more languages/layouts than this, but even if they do it will still work; performance would just be a little worse due to being uncached.
static HKL sTargetKeybdLayout; // Set by SendKeys() for use by the functions it calls directly and indirectly.
static ResultType sTargetLayoutHasAltGr; //
// v1.0.43: Support for SendInput() and journal-playback hook:
#define MAX_INITIAL_EVENTS_SI 500UL // sizeof(INPUT) == 28 as of 2006. Since Send is called so often, and since most Sends are short, reducing the load on the stack is also a deciding factor for these.
#define MAX_INITIAL_EVENTS_PB 1500UL // sizeof(PlaybackEvent) == 8, so more events are justified before resorting to malloc().
static LPINPUT sEventSI; // No init necessary. An array that's allocated/deallocated by SendKeys().
//else no foreground window, so keep keybd_layout_thread at default.
}
sTargetKeybdLayout = GetKeyboardLayout(keybd_layout_thread); // If keybd_layout_thread==0, this will get our thread's own layout, which seems like the best/safest default.
sTargetLayoutHasAltGr = LayoutHasAltGr(sTargetKeybdLayout); // Note that WM_INPUTLANGCHANGEREQUEST is not monitored by MsgSleep for the purpose of caching our thread's keyboard layout. This is because it would be unreliable if another msg pump such as MsgBox is running. Plus it hardly helps perf. at all, and hurts maintainability.
// Below is now called with "true" so that the hook's modifier state will be corrected (if necessary)
// prior to every send.
modLR_type mods_current = GetModifierLRState(true); // Current "logical" modifier state.
// Make a best guess of what the physical state of the keys is prior to starting (there's no way
// to be certain without the keyboard hook). Note: We only want those physical
// keys that are also logically down (it's possible for a key to be down physically
// but not logically such as when R-control, for example, is a suffix hotkey and the
bool in_blind_mode; // For performance and also to reserve future flexibility, recognize {Blind} only when it's the first item in the string.
if (in_blind_mode = !aSendRaw && !strnicmp(aKeys, "{Blind}", 7)) // Don't allow {Blind} while in raw mode due to slight chance {Blind} is intended to be sent as a literal string.
{
// Blind Mode (since this seems too obscure to document, it's mentioned here): Blind Mode relies
// on modifiers already down for something like ^c because ^c is saying "manifest a ^c", which will
// happen if ctrl is already down. By contrast, Blind does not release shift to produce lowercase
// letters because avoiding that adds flexibility that couldn't be achieved otherwise.
// Thus, ^c::Send {Blind}c produces the same result when ^c is substituted for the final c.
// But Send {Blind}{LControl down} will generate the extra events even if ctrl already down.
aKeys += 7; // Remove "{Blind}" from further consideration (essential for "SendRaw {Blind}").
// The following value is usually zero unless the user is currently holding down
// some modifiers as part of a hotkey. These extra modifiers are the ones that
// this send operation (along with all its calls to SendKey and similar) should
// consider to be down for the duration of the Send (unless they go up via an
: TOGGLE_INVALID; // In blind mode, don't do store capslock (helps remapping and also adds flexibility).
}
else // OS is Win9x and threads are not attached.
{
// Attempt to turn off capslock, but never attempt to turn it back on because we can't
// reliably detect whether it was on beforehand. Update: This didn't do any good, so
// it's disabled for now:
//CapslockOffWin9x();
prior_capslock_state = TOGGLE_INVALID;
}
int orig_key_delay = g.KeyDelay;
int orig_press_duration = g.PressDuration;
if (aSendModeOrig == SM_INPUT || aSendModeOrig == SM_INPUT_FALLBACK_TO_PLAY)
{
// Both of these modes fall back to a different mode depending on whether some other script
// is running with a keyboard/mouse hook active. Of course, the detection of this isn't foolproof
// because older versions of AHK may be running and/or other apps with LL keyboard hooks. It's
// just designed to add a lot of value for typical usage because SendInput is prefered due to it
// being considerably faster than SendPlay, especially for long replacements when the CPU is under
// heavy load.
if ( !sMySendInput // Win95/NT-pre-SP3 don't support SendInput, so fall back to the specified mode.
|| SystemHasAnotherKeybdHook() // This function has been benchmarked to ensure it doesn't yield our timeslice, etc. 200 calls take 0ms according to tick-count, even when CPU is maxed.
|| !aSendRaw && SystemHasAnotherMouseHook() && strcasestr(aKeys, "{Click") ) // Ordered for short-circuit boolean performance. v1.0.43.09: Fixed to be strcasestr vs. !strcasestr
{
// Need to detect in advance what type of array to build (for performance and code size). That's why
// it done this way, and here are the comments about it:
// strcasestr() above has an unwanted amount of overhead if aKeys is huge, but it seems acceptable
// because it's called only when system has another mouse hook but *not* another keybd hook (very rare).
// Also, for performance reasons, {LButton and such are not checked for, which is documented and seems
// justified because the new {Click} method is expected to become prevalent, especially since this
// whole section only applies when the new SendInput mode is in effect.
// Finally, checking aSendRaw isn't foolproof because the string might contain {Raw} prior to {Click,
// but the complexity and performance of checking for that seems unjustified given the rarity,
// especially since there are almost never any consequences to reverting to hook mode vs. SendInput.
if (aSendModeOrig == SM_INPUT_FALLBACK_TO_PLAY)
aSendModeOrig = SM_PLAY;
else // aSendModeOrig == SM_INPUT, so fall back to EVENT.
{
aSendModeOrig = SM_EVENT;
// v1.0.43.08: When SendInput reverts to SendEvent mode, the majority of users would want
// a fast sending rate that is more comparable to SendInput's speed that the default KeyDelay
// of 10ms. PressDuration may be generally superior to KeyDelay because it does a delay after
// each changing of modifier state (which tends to improve reliability for certain apps).
// The following rules seem likely to be the best benefit in terms of speed and reliability:
goto brace_case_end; // Gets rid of one level of indentation. Well worth it.
if (vk || sc)
{
if (key_as_modifiersLR = KeyToModifiersLR(vk, sc)) // Assign
{
if (!aTargetWindow)
{
if (event_type == KEYDOWN) // i.e. make {Shift down} have the same effect {ShiftDown}
{
this_event_modifier_down = vk;
if (key_down_is_persistent) // v1.0.44.05.
sModifiersLR_persistent |= key_as_modifiersLR;
persistent_modifiers_for_this_SendKeys |= key_as_modifiersLR; // v1.0.44.06: Added this line to fix the fact that "DownTemp" should keep the key pressed down after the send.
}
else if (event_type == KEYUP) // *not* KEYDOWNANDUP, since that would be an intentional activation of the Start Menu or menu bar.
{
DisguiseWinAltIfNeeded(vk, in_blind_mode);
sModifiersLR_persistent &= ~key_as_modifiersLR;
// By contrast with KEYDOWN, KEYUP should also remove this modifier
// from extra_persistent_modifiers_for_blind_mode if it happens to be
// in there. For example, if "#i::Send {LWin Up}" is a hotkey,
// LWin should become persistently up in every respect.
// v1.0.40: SendKeySpecial sends only keybd_event keystrokes, not ControlSend style keystrokes:
if (!aTargetWindow) // In this mode, mods_for_next_key is ignored due to being unsupported.
SendKeySpecial(*aKeys, 1);
//else do nothing since it's there's no known way to send the keystokes.
}
mods_for_next_key = 0; // Safest to reset this regardless of whether a key was sent.
}
} // for()
modLR_type mods_to_set;
if (sSendMode)
{
int final_key_delay = -1; // Set default.
if (!sAbortArraySend && sEventCount > 0) // Check for zero events for performance, but more importantly because playback hook will not operate correctly with zero.
{
// Add more events to the array (prior to sending) to support the following:
// Restore the modifiers to match those the user is physically holding down, but do it as *part*
// of the single SendInput/Play call. The reasons it's done here as part of the array are:
// 1) It avoids the need for #HotkeyModifierTimeout (and it's superior to it) for both SendInput
// and SendPlay.
// 2) The hook will not be present during the SendInput, nor can it be reinstalled in time to
// catch any physical events generated by the user during the Send. Consequently, there is no
// known way to reliably detect physical keystate changes.
// 3) Changes made to modifier state by SendPlay are seen only by the active window's thread.
// Thus, it would be inconsistent and poasibly incorrect to adjust global modifier state
// after (or during) a SendPlay.
// So rather than resorting to #HotkeyModifierTimeout, we can restore the modifiers within the
// protection of SendInput/Play's uninterruptibility, allowing the user's buffered keystrokes
// (if any) to hit against the correct modifier state when the SendInput/Play completes.
// For example, if #c:: is a hotkey and the user releases Win during the SendInput/Play, that
// release would hit after SendInput/Play restores Win to the down position, and thus Win would
// not be stuck down. Furthermore, if the user didn't release Win, Win would be in the
// correct/intended position.
// This approach has a few weaknesses (but the strengths appear to outweigh them):
// 1) Hitting SendInput's 5000 char limit would omit the tail-end keystrokes, which would mess up
// all the assumptions here. But hitting that limit should be very rare, especially since it's
// documented and thus scripts will avoid it.
// 2) SendInput's assumed uninterruptibility is false if any other app or script has an LL hook
// installed. This too is documented, so scripts should generally avoid using SendInput when
// they know there are other LL hooks in the system. In any case, there's no known solution
| (in_blind_mode ? 0 : (mods_down_physically_orig & ~mods_down_physically_but_not_logically_orig)); // The last item is usually 0.
// Above: When in blind mode, don't restore physical modifiers. This is done to allow a hotkey
// such as the following to release Shift:
// +space::SendInput/Play {Blind}{Shift up}
// Note that SendPlay can make such a change only from the POV of the target window; i.e. it can
// release shift as seen by the target window, but not by any other thread; so the shift key would
// still be considered to be down for the purpose of firing hotkeys (it can't change global key state
// as seen by GetAsyncKeyState).
// For more explanation of above, see a similar section for the non-array/old Send below.
SetModifierLRState(mods_to_set, sEventModifiersLR, NULL, true, true); // Disguise in case user released or pressed Win/Alt during the Send (seems best to do it even for SendPlay, though it probably needs only Alt, not Win).
// mods_to_set is used further below as the set of modifiers that were explicitly put into effect at the tail end of SendInput.
SendEventArray(final_key_delay, mods_to_set);
}
CleanupEventArray(final_key_delay);
}
else // A non-array send is in effect, so a more elaborate adjustment to logical modifiers is called for.
{
// Determine (or use best-guess, if necessary) which modifiers are down physically now as opposed
// to right before the Send began.
modLR_type mods_down_physically; // As compared to mods_down_physically_orig.
if (g_KeybdHook)
mods_down_physically = g_modifiersLR_physical;
else // No hook, so consult g_HotkeyModifierTimeout to make the determination.
// Assume that the same modifiers that were phys+logically down before the Send are still
// physically down (though not necessarily logically, since the Send may have released them),
// but do this only if the timeout period didn't expire (or the user specified that it never
// times out; i.e. elapsed time < timeout-value; DWORD math gives the right answer even if
// tick-count has wrapped around).
mods_down_physically = (g_HotkeyModifierTimeout < 0 // It never times out or...
|| (GetTickCount() - g_script.mThisHotkeyStartTime) < (DWORD)g_HotkeyModifierTimeout) // It didn't time out.
? mods_down_physically_orig : 0;
// Restore the state of the modifiers to be those the user is physically holding down right now.
// Any modifiers that are logically "persistent", as detected upon entrance to this function
// (e.g. due to something such as a prior "Send, {LWinDown}"), are also pushed down if they're not already.
// Don't press back down the modifiers that were used to trigger this hotkey if there's
// any doubt that they're still down, since doing so when they're not physically down
// would cause them to be stuck down, which might cause unwanted behavior when the unsuspecting
// user resumes typing.
// v1.0.42.04: Now that SendKey() is lazy about releasing Ctrl and/or Shift (but not Win/Alt),
// the section below also releases Ctrl/Shift if appropriate. See SendKey() for more details.
mods_to_set = persistent_modifiers_for_this_SendKeys; // Set default.
if (in_blind_mode) // This section is not needed for the array-sending modes because they exploit uninterruptibility to perform a more reliable restoration.
{
// At the end of a blind-mode send, modifiers are restored differently than normal. One
// reason for this is to support the explicit ability for a Send to turn off a hotkey's
// modifiers even if the user is still physically holding them down. For example:
// #space::Send {LWin up} ; Fails to release it, by design and for backward compatibility.
// #space::Send {Blind}{LWin up} ; Succeeds, allowing LWin to be logically up even though it's physically down.
event.paramH = source_event.sc & 0xFF; // 0xFF omits the extended-key-bit, if present.
if (source_event.sc & 0x100) // It's an extended key.
event.paramH |= 0x8000; // So mark it that way using EVENTMSG's convention.
// Notes about inability of playback to simulate LWin and RWin in a way that performs their native funcion:
// For the following reasons, it seems best not to send LWin/RWin via keybd_event inside the playback hook:
// 1) Complexities such as having to check for an array that consists entirely of LWin/RWin events,
// in which case the playback hook mustn't be activated because it requires that we send
// at least one event through it. Another complexity is that all keys modified by Win would
// have to be flagged in the array as needing to be sent via keybd_event.
// 2) It might preserve some flexibilility to be able to send LWin/RWin events directly to awindow,
// similiar to ControlSend (perhaps for shells other than Explorer, who might allow apps to make
// use of LWin/RWin internally). The window should receive LWIN/RWIN as WM_KEYDOWN messages when
// sent via playback. Note: unlike the neutral SHIFT/ALT/CTRL keys, which are detectible via the
// target thread's call to GetKeyState(), LWin and RWin aren't detectible that way.
// 3) Code size and complexity.
//
// Related: LWin and RWin are released and pressed down during playback for simplicity and also
// on the off-chance the target window takes note of the incoming WM_KEYDOWN on VK_LWIN/RWIN and
// changes state until the up-event is received (however, the target thread's call of GetKeyState
// can't see a any effect for hook-sent LWin/RWin).
//
// Related: If LWin or RWin is logically down at start of SendPlay, SendPlay's events won't be
// able to release it from the POV of the target thread's calls to GetKeyState(). That might mess
// things up for apps that check the logical state of the Win keys. But due to rarity: in those
// cases, a workaround would be to do an explicit old-style Send {Blind} (as the first line of the
// hotkey) to release the modifier logically prior to SendPlay commands.
//
// Related: Although some apps might not like receiving SendPlay's LWin/RWin if shell==Explorer
// (since there may be no normal way for such keystrokes to arrive as WM_KEYDOWN events) maybe it's
// best not to omit/ignore LWin/RWin if it is possible in other shells, or adds flexibility.
// After all, sending {LWin/RWin} via hook should be rare, especially if it has no effect (except
// for cases where a Win hotkey releases LWin as part of SendPlay, but even that can be worked
// around via an explicit Send {Blind}{LWin up} beforehand).
}
else // MOUSE EVENT.
{
// Unlike keybd_event() and SendInput(), explicit coordintes must be specified for each mouse event.
// The builder of this array must ensure that coordinates are valid or set to COORD_UNSPECIFIED_SHORT.
if (source_event.x == COORD_UNSPECIFIED_SHORT || has_coord_offset)
{
// For simplicity with calls such as WindowToScreen(), the one who set up this array has ensured
// that both X and Y are either COORD_UNSPECIFIED_SHORT or not so (i.e. not a combination).
// Since the user nor anything else can move the cursor during our playback, GetCursorPos()
// should accurately reflect the position set by any previous mouse-move done by this playback.
// This seems likely to be true even for DirectInput games, though hasn't been tested yet.
POINT cursor;
GetCursorPos(&cursor);
event.paramL = cursor.x;
event.paramH = cursor.y;
if (has_coord_offset) // The specified coordinates are offsets to be applied to the cursor's current position.
{
event.paramL += source_event.x;
event.paramH += source_event.y;
// Update source array in case HC_GETNEXT is called again for this same event, in which case
// don't want to apply the offset again (the has-offset flag has already been removed from the
// source event higher above).
source_event.x = event.paramL;
source_event.y = event.paramH;
sThisEventIsScreenCoord = true; // Mark the above as absolute vs. relative in case HC_GETNEXT is called again for this event.
}
}
else
{
event.paramL = source_event.x;
event.paramH = source_event.y;
if (!(g.CoordMode & COORD_MODE_MOUSE) && !sThisEventIsScreenCoord) // Coordinates are relative to the window that is active now (during realtime playback).
return 0; // No CallNextHookEx(). See comments further below.
} // case HC_GETNEXT.
case HC_SKIP: // Advance to the next mouse/keyboard event, if any.
// Advance to the next item, which is either a delay or an event (preps for next HC_GETNEXT).
++sCurrentEvent;
// Although caller knows it has to do the tail-end delay (if any) since there's no way to
// do a trailing delay at the end of playback, it may have put a delay at the end of the
// array anyway for code simplicity. For that reason and maintainability:
// Skip over any delays that are present to discover if there is a next event.
UINT u;
for (u = sCurrentEvent; u < sEventCount && !sEventPB[u].message; ++u);
if (u == sEventCount) // No more events.
{
// MSDN implies in the following statement that it's acceptable (and perhaps preferable in
// the case of a playback hook) for the hook to unhook itself: "The hook procedure can be in the
// state of being called by another thread even after UnhookWindowsHookEx returns."
UnhookWindowsHookEx(g_PlaybackHook);
g_PlaybackHook = NULL; // Signal the installer of the hook that it's gone now.
// The following is an obsolete method from pre-v1.0.44. Do not reinstate it without adding handling
// to MainWindowProc() to do "g_PlaybackHook = NULL" upon receipt of WM_CANCELJOURNAL.
// PostMessage(g_hWnd, WM_CANCELJOURNAL, 0, 0); // v1.0.44: Post it to g_hWnd vs. NULL because it's a little safer (SEE COMMENTS in MsgSleep's WM_CANCELJOURNAL for why it's almost completely safe with NULL).
// Above: By using WM_CANCELJOURNAL instead of a custom message, the creator of this hook can easily
// use a message filter to watch for both a system-generated removal of the hook (via the user
// pressing Ctrl-Esc. or Ctrl-Alt-Del) or one we generate here (though it's currently not implemented
// that way because it would prevent journal playback to one of our thread's own windows).
}
else
sFirstCallForThisEvent = true; // Reset to prepare for next HC_GETNEXT.
return 0; // MSDN: The return value is used only if the hook code is HC_GETNEXT; otherwise, it is ignored.
default:
// Covers the following cases:
//case HC_NOREMOVE: // MSDN: An application has called the PeekMessage function with wRemoveMsg set to PM_NOREMOVE, indicating that the message is not removed from the message queue after PeekMessage processing.
//case HC_SYSMODALON: // MSDN: A system-modal dialog box is being displayed. Until the dialog box is destroyed, the hook procedure must stop playing back messages.
//case HC_SYSMODALOFF: // MSDN: A system-modal dialog box has been destroyed. The hook procedure must resume playing back the messages.
//case(...aCode < 0...): MSDN docs specify that the hook should return in this case.
//
// MS gives some sample code at http://support.microsoft.com/default.aspx?scid=KB;EN-US;124835
// about the proper values to return to avoid hangs on NT (it seems likely that this implementation
// is compliant enough if you read between the lines). Their sample code indicates that
// "return CallNextHook()" should be done for basically everything except HC_SKIP/HC_GETNEXT, so
// as of 1.0.43.08, that is what is done here.
// Testing shows that when a so-called system modial dialog is displayed (even if it isn't the
// active window) playback stops automatically, probably because the system doesn't call the hook
// during such times (only a "MsgBox 4096" has been tested so far).
//
// The first parameter uses g_PlaybackHook rather than NULL because MSDN says it's merely
// "currently ignored", but in the older "Win32 hooks" article, it says that the behavior
if (event.message >= WM_MOUSEFIRST && event.message <= WM_MOUSELAST) // Mouse event, including wheel.
{
if (event.message != WM_MOUSEMOVE)
{
// WHEEL: No info comes in about which direction the wheel was turned (nor by how many notches).
// In addition, it appears impossible to specify such info when playing back the event.
// Therefore, playback usually produces downward wheel movement (but upward in some apps like
// Visual Studio).
dest_event.x = event.paramL;
dest_event.y = event.paramH;
++sEventCount;
}
}
else // Keyboard event.
{
dest_event.vk = event.paramL & 0x00FF;
dest_event.sc = (event.paramL & 0xFF00) >> 8;
if (event.paramH & 0x8000) // Extended key.
dest_event.sc |= 0x100;
if (dest_event.vk == VK_CANCEL) // Ctrl+Break.
{
UnhookWindowsHookEx(g_PlaybackHook);
g_PlaybackHook = NULL; // Signal the installer of the hook that it's gone now.
// Obsolete method, pre-v1.0.44:
//PostMessage(g_hWnd, WM_CANCELJOURNAL, 0, 0); // v1.0.44: Post it to g_hWnd vs. NULL so that it isn't lost when script is displaying a MsgBox or other dialog.
}
++sEventCount;
}
break;
}
//case HC_SYSMODALON: // A system-modal dialog box is being displayed. Until the dialog box is destroyed, the hook procedure must stop playing back messages.
//case HC_SYSMODALOFF: // A system-modal dialog box has been destroyed. The hook procedure must resume playing back the messages.
// break;
}
// Unlike the playback hook, it seems more correct to call CallNextHookEx() unconditionally so that
// any other journal record hooks can also record the event. But MSDN is quite vague about this.
// Do this only after the above, so that the SC is left/right specific if the VK was such,
// even on Win9x (though it's probably never called that way for Win9x; it's probably aways
// called with either just the proper left/right SC or that plus the neutral VK).
// Under WinNT/2k/XP, sending VK_LCONTROL and such result in the high-level (but not low-level
// I think) hook receiving VK_CONTROL. So somewhere interally it's being translated (probably
// by keybd_event itself). In light of this, translate the keys here manually to ensure full
// support under Win9x (which might not support this internal translation). The scan code
// looked up above should still be correct for left-right centric keys even under Win9x.
// v1.0.43: Apparently, the journal playback hook also requires neutral modifier keystrokes
// rather than left/right ones. Otherwise, the Shift key can't capitalize letters, etc.
if (sSendMode == SM_PLAY || g_os.IsWin9x())
{
// Convert any non-neutral VK's to neutral for these OSes, since apps and the OS itself
// can't be expected to support left/right specific VKs while running under Win9x:
switch(aVK)
{
case VK_LCONTROL:
case VK_RCONTROL: aVK = VK_CONTROL; break; // But leave scan code set to a left/right specific value rather than converting it to "left" unconditionally.
case VK_LSHIFT:
case VK_RSHIFT: aVK = VK_SHIFT; break;
case VK_LMENU:
case VK_RMENU: aVK = VK_MENU; break;
}
}
// aTargetWindow is almost always passed in as NULL by our caller, even if the overall command
// being executed is ControlSend. This is because of the following reasons:
// 1) Modifiers need to be logically changed via keybd_event() when using ControlSend against
// a cmd-prompt, console, or possibly other types of windows.
// 2) If a hotkey triggered the ControlSend that got us here and that hotkey is a naked modifier
// such as RAlt:: or modified modifier such as ^#LShift, that modifier would otherwise auto-repeat
// an possibly interfere with the send operation. This auto-repeat occurs because unlike a normal
// send, there are no calls to keybd_event() (keybd_event() stop the auto-repeat as a side-effect).
// One exception to this is something like "ControlSend, Edit1, {Control down}", which explicitly
// calls us with a target window. This exception is by design and has been bug-fixed and documented
// in ControlSend for v1.0.21:
if (aTargetWindow) // This block should be thread-safe because hook thread never calls it in this mode.
{
if (KeyToModifiersLR(aVK, aSC))
{
// When sending modifier keystrokes directly to a window, use the AutoIt3 SetKeyboardState()
// technique to improve the reliability of changes to modifier states. If this is not done,
// sometimes the state of the SHIFT key (and perhaps other modifiers) will get out-of-sync
// with what's intended, resulting in uppercase vs. lowercase problems (and that's probably
// just the tip of the iceberg). For this to be helpful, our caller must have ensured that
// our thread is attached to aTargetWindow's (but it seems harmless to do the below even if
// that wasn't done for any reason). Doing this here in this function rather than at a
// higher level probably isn't best in terms of performance (e.g. in the case where more
// than one modifier is being changed, the multiple calls to Get/SetKeyboardState() could
// be consolidated into one call), but it is much easier to code and maintain this way
// since many different functions might call us to change the modifier state:
BYTE state[256];
GetKeyboardState((PBYTE)&state);
if (aEventType == KEYDOWN)
state[aVK] |= 0x80;
else if (aEventType == KEYUP)
state[aVK] &= ~0x80;
// else KEYDOWNANDUP, in which case it seems best (for now) not to change the state at all.
// It's rarely if ever called that way anyway.
// If aVK is a left/right specific key, be sure to also update the state of the neutral key:
switch(aVK)
{
case VK_LCONTROL:
case VK_RCONTROL:
if ((state[VK_LCONTROL] & 0x80) || (state[VK_RCONTROL] & 0x80))
state[VK_CONTROL] |= 0x80;
else
state[VK_CONTROL] &= ~0x80;
break;
case VK_LSHIFT:
case VK_RSHIFT:
if ((state[VK_LSHIFT] & 0x80) || (state[VK_RSHIFT] & 0x80))
state[VK_SHIFT] |= 0x80;
else
state[VK_SHIFT] &= ~0x80;
break;
case VK_LMENU:
case VK_RMENU:
if ((state[VK_LMENU] & 0x80) || (state[VK_RMENU] & 0x80))
state[VK_MENU] |= 0x80;
else
state[VK_MENU] &= ~0x80;
break;
}
SetKeyboardState((PBYTE)&state);
// Even after doing the above, we still continue on to send the keystrokes
// themselves to the window, for greater reliability (same as AutoIt3).
}
// lowest 16 bits: repeat count: always 1 for up events, probably 1 for down in our case.
// highest order bits: 11000000 (0xC0) for keyup, usually 00000000 (0x00) for keydown.
LPARAM lParam = (LPARAM)(aSC << 16);
if (aEventType != KEYUP) // i.e. always do it for KEYDOWNANDUP
if (do_detect_altgr = hookable_ralt = (aVK == VK_RMENU && !put_event_into_array && g_KeybdHook)) // This is an RALT that the hook will be able to monitor. Using VK_RMENU vs. VK_MENU should be safe since this logic is only needed for the hook, which is never in effect on Win9x.
{
if (!caller_is_keybd_hook) // sTargetKeybdLayout is set/valid only by SendKeys().
target_keybd_layout = sTargetKeybdLayout;
else
{
// Below is similar to the macro "Get_active_window_keybd_layout":
? GetWindowThreadProcessId(active_window, NULL) : 0); // When no foreground window, the script's own layout seems like the safest default.
target_layout_has_altgr = LayoutHasAltGr(target_keybd_layout); // In the case of this else's "if", target_layout_has_altgr was already set properly higher above.
}
if (target_layout_has_altgr != LAYOUT_UNDETERMINED) // This layout's AltGr status is already known with certainty.
do_detect_altgr = false; // So don't go through the detection steps here and other places later below.
// Above also updates sTargetLayoutHasAltGr in cases where target_layout_has_altgr is an alias/reference for it.
}
}
if (aVK == VK_NUMLOCK && g_os.IsWin9x()) // Under Win9x, Numlock needs special treatment.
ToggleNumlockWin9x();
if (do_key_history)
UpdateKeyEventHistory(false, aVK, aSC); // Should be thread-safe since if no hook means only one thread ever sends keystrokes (with possible exception of mouse hook, but that seems too rare).
}
// The press-duration delay is done only when this is a down-and-up because otherwise,
// the normal g.KeyDelay will be in effect. In other words, it seems undesirable in
// most cases to do both delays for only "one half" of a keystroke:
if (aDoKeyDelay && aEventType == KEYDOWNANDUP) // Hook should never specify a delay, so no need to check if caller is hook.
DoKeyDelay(sSendMode == SM_PLAY ? g.PressDurationPlay : g.PressDuration); // DoKeyDelay() is not thread safe but since the hook thread should never pass true for aKeyDelay, it shouldn't be an issue.
if (aEventType != KEYDOWN) // i.e. always do it for KEYDOWNANDUP
if (do_selective_blockinput) // It seems best NOT to use g_BlockMouseMove for this, since often times the user would want keyboard input to be disabled too, until after the mouse event is done.
Line::ScriptBlockInput(true); // Turn it on unconditionally even if it was on, since Ctrl-Alt-Del might have disabled it.
switch (aActionType)
{
case ACT_MOUSEMOVE:
DWORD unused;
MouseMove(aX1, aY1, unused, aSpeed, aMoveOffset); // Does nothing if coords are invalid.
break;
case ACT_MOUSECLICK:
MouseClick(aVK, aX1, aY1, aRepeatCount, aSpeed, aEventType, aMoveOffset); // Does nothing if coords are invalid.
break;
case ACT_MOUSECLICKDRAG:
MouseClickDrag(aVK, aX1, aY1, aX2, aY2, aSpeed, aMoveOffset); // Does nothing if coords are invalid.
break;
} // switch()
if (sSendMode)
{
int final_key_delay = -1; // Set default.
if (!sAbortArraySend && sEventCount > 0)
SendEventArray(final_key_delay, 0); // Last parameter is ignored because keybd hook isn't removed for a pure-mouse SendInput.
CleanupEventArray(final_key_delay);
}
if (do_selective_blockinput && !blockinput_prev) // Turn it back off only if it was off before we started.
Line::ScriptBlockInput(false);
}
void MouseClickDrag(vk_type aVK, int aX1, int aY1, int aX2, int aY2, int aSpeed, bool aMoveOffset)
{
// Check if one of the coordinates is missing, which can happen in cases where this was called from
// a source that didn't already validate it. Can't call Line::ValidateMouseCoords() because that accepts strings.
// Must pass VK_CONTROL rather than VK_LCONTROL because playback hook requires neutral modifiers.
PutKeybdEventIntoArray(MOD_LCONTROL, VK_CONTROL, SC_LCONTROL, aEventFlags, aExtraInfo); // Recursive call to self.
// Above must be done prior to the capacity check below because above might add a new array item.
if (sEventCount == sMaxEvents) // Array's capacity needs expanding.
if (!ExpandEventArray())
return;
// Keep track of the predicted modifier state for use in other places:
if (key_up)
sEventModifiersLR &= ~aKeyAsModifiersLR;
else
sEventModifiersLR |= aKeyAsModifiersLR;
if (sSendMode == SM_INPUT)
{
INPUT &this_event = sEventSI[sEventCount]; // For performance and convenience.
this_event.type = INPUT_KEYBOARD;
this_event.ki.wVk = aVK;
this_event.ki.wScan = LOBYTE(aSC);
this_event.ki.dwFlags = aEventFlags;
this_event.ki.dwExtraInfo = aExtraInfo; // Although our hook won't be installed (or won't detect, in the case of playback), that of other scripts might be, so set this for them.
this_event.ki.time = 0; // Let the system provide its own timestamp, which might be more accurate for individual events if this will be a very long SendInput.
sHooksToRemoveDuringSendInput |= HOOK_KEYBD; // Presence of keyboard hook defeats uninterruptibility of keystrokes.
}
else // Playback hook.
{
PlaybackEvent &this_event = sEventPB[sEventCount]; // For performance and convenience.
if (!(aVK || aSC)) // Caller is signaling that aExtraInfo contains a delay/sleep event.
{
// Although delays at the tail end of the playback array can't be implemented by the playback
// itself, caller wants them put in too.
this_event.message = 0; // Message number zero flags it as a delay rather than an actual event.
this_event.time_to_wait = aExtraInfo;
}
else // A normal (non-delay) event for playback.
{
// By monitoring incoming events in a message/event loop, the following key combinations were
// confirmed to be WM_SYSKEYDOWN vs. WM_KEYDOWN (up events weren't tested, so are assumed to
// be the same as down-events):
// Alt+Win
// Alt+Shift
// Alt+Capslock/Numlock/Scrolllock
// Alt+AppsKey
// Alt+F2/Delete/Home/End/Arrow/BS
// Alt+Space/Enter
// Alt+Numpad (tested all digits & most other keys, with/without Numlock ON)
|| (sEventModifiersLR & (MOD_LALT | MOD_RALT)) && key_up) // ... or this is the release of Alt (for simplicity, assume that Alt modified something while it was down).
// This function is designed to be called from only one thread (the main thread) since it's not thread-safe.
// If the array-type is journal playback, caller should include MOUSEEVENTF_ABSOLUTE in aEventFlags if the
// the mouse coordinates aX and aY are relative to the screen rather than the active window.
{
if (sEventCount == sMaxEvents) // Array's capacity needs expanding.
if (!ExpandEventArray())
return;
if (sSendMode == SM_INPUT)
{
INPUT &this_event = sEventSI[sEventCount]; // For performance and convenience.
this_event.type = INPUT_MOUSE;
this_event.mi.dx = (aX == COORD_UNSPECIFIED) ? 0 : aX; // v1.0.43.01: Must be zero if no change in position is
this_event.mi.dy = (aY == COORD_UNSPECIFIED) ? 0 : aY; // desired (fixes compatibility with certain apps/games).
this_event.mi.dwFlags = aEventFlags;
this_event.mi.mouseData = aData;
this_event.mi.dwExtraInfo = KEY_IGNORE; // Although our hook won't be installed (or won't detect, in the case of playback), that of other scripts might be, so set this for them.
this_event.mi.time = 0; // Let the system provide its own timestamp, which might be more accurate for individual events if this will be a very long SendInput.
sHooksToRemoveDuringSendInput |= HOOK_MOUSE; // Presence of mouse hook defeats uninterruptibility of mouse clicks/moves.
}
else // Playback hook.
{
// Note: Delay events (sleeps), which are supported in playback mode but not SendInput, are always inserted
// via PutKeybdEventIntoArray() rather than this function.
PlaybackEvent &this_event = sEventPB[sEventCount]; // For performance and convenience.
// Determine the type of event specified by caller, but also omit MOUSEEVENTF_MOVE so that the
// follow variations can be differentiated:
// 1) MOUSEEVENTF_MOVE by itself.
// 2) MOUSEEVENTF_MOVE with a click event or wheel turn (in this case MOUSEEVENTF_MOVE is permitted but
// not required, since all mouse events in playback mode must have explicit coordinates at the
// time they're playbed back).
// 3) A click event or wheel turn by itself (same remark as above).
// Bits are isolated in what should be a future-proof way (also omits MSG_OFFSET_MOUSE_MOVE bit).
// Caller has ensured that sMySendInput isn't NULL.
sMySendInput(sEventCount, sEventSI, sizeof(INPUT)); // Must call dynamically-resolved version for Win95/NT compatibility.
// The return value is ignored because it never seems to be anything other than sEventCount, even if
// the Send seems to partially fail (e.g. due to hitting 5000 event maximum).
// Typical speed of SendInput: 10ms or less for short sends (under 100 events).
// Typically 30ms for 500 events; and typically no more than 200ms for 5000 events (which is
// the apparent max).
// Testing shows that when SendInput returns to its caller, all of its key states are in effect
// even if the target window hasn't yet had time to receive them all. For example, the
// following reports that LShift is down:
// SendInput {a 4900}{LShift down}
// MsgBox % GetKeyState("LShift")
// Furthermore, if the user manages to physically press or release a key during the call to
// SendInput, testing shows that such events are in effect immediately when SendInput returns
// to its caller, perhaps because SendInput clears out any backlog of physical keystrokes prior to
// returning, or perhaps because the part of the OS that updates key states is a very high priority.
if (active_hooks)
{
if (active_hooks & sHooksToRemoveDuringSendInput & HOOK_KEYBD) // Keyboard hook was actually removed during SendInput.
{
// The above call to SendInput() has not only sent its own events, it has also emptied
// the buffer of any events generated outside but during the SendInput. Since such
// events are almost always physical events rather than simulated ones, it seems to do
// more good than harm on average to consider any such changes to be physical.
// The g_PhysicalKeyState array is also updated by GetModifierLRState(true), but only
// for the modifier keys, not for all keys on the keyboard. Even if adjust all keys
// is possible, it seems overly complex and it might impact performance more than it's
// worth given the rarity of the user changing physical key states during a SendInput
// and then wanting to explicitly retrieve that state via GetKeyState(Key, "P").
modLR_type mods_current = GetModifierLRState(true); // This also serves to correct the hook's logical modifiers, since hook was absent during the SendInput.
bool defer_alt_release = ctrl_not_down && !ctrl_will_not_be_down; // i.e. Ctrl not down but it will be.
bool release_shift_before_alt_ctrl = defer_alt_release // i.e. Control is moving into the down position or...
|| !(aModifiersLRnow & (MOD_LALT | MOD_RALT)) && (aModifiersLRnew & (MOD_LALT | MOD_RALT)); // ...Alt is moving into the down position.
// Concerning "release_shift_before_alt_ctrl" above: Its purpose is to prevent unwanted firing of the OS's
// language bar hotkey. See the bottom of this function for more explanation.
// ALT:
bool disguise_alt_down = aDisguiseDownWinAlt && ctrl_not_down && ctrl_will_not_be_down; // Since this applies to both Left and Right Alt, don't take sTargetLayoutHasAltGr into account here. That is done later below.
// WIN: The WIN key is successfully disguised under a greater number of conditions than ALT.
// Since SendPlay can't display Start Menu, there's no need to send the disguise-keystrokes (such
// keystrokes might cause unwanted effects in certain games):
// Handle ALT and WIN prior to the other modifiers because the "disguise" methods below are
// only needed upon release of ALT or WIN. This is because such releases tend to have a better
// chance of being "disguised" if SHIFT or CTRL is down at the time of the release. Thus, the
// release of SHIFT or CTRL (if called for) is deferred until afterward.
// ** WIN
// Must be done before ALT in case it is relying on ALT being down to disguise the release WIN.
// If ALT is going to be pushed down further below, defer_win_release should be true, which will make sure
// the WIN key isn't released until after the ALT key is pushed down here at the top.
// Also, WIN is a little more troublesome than ALT, so it is done first in case the ALT key
// is down but will be going up, since the ALT key being down might help the WIN key.
// For example, if you hold down CTRL, then hold down LWIN long enough for it to auto-repeat,
// then release CTRL before releasing LWIN, the Start Menu would appear, at least on XP.
// But it does not appear if CTRL is released after LWIN.
// Also note that the ALT key can disguise the WIN key, but not vice versa.
if (release_lwin)
{
if (!defer_win_release)
{
// Fixed for v1.0.25: To avoid triggering the system's LAlt+Shift language hotkey, the
// Control key is now used to suppress LWIN/RWIN (preventing the Start Menu from appearing)
// rather than the Shift key. This is definitely needed for ALT, but is done here for
// WIN also in case ALT is down, which might cause the use of SHIFT as the disguise key
// to trigger the language switch.
if (ctrl_nor_shift_nor_alt_down && aDisguiseUpWinAlt // Nor will they be pushed down later below, otherwise defer_win_release would have been true and we couldn't get to this point.
&& sSendMode != SM_PLAY) // SendPlay can't display Start Menu, so disguise not needed (also, disguise might mess up some games).
if (!defer_alt_release || sTargetLayoutHasAltGr == CONDITION_TRUE) // No need to defer if RAlt==AltGr. But don't change the value of defer_alt_release because LAlt uses it too.
{
if (sTargetLayoutHasAltGr == CONDITION_TRUE)
{
// Indicate that control is up now, since the release of AltGr will cause that indirectly.
// Fix for v1.0.43: Unlike the pressing down of AltGr in a later section, which callers want
// to automatically press down LControl too (by the very nature of AltGr), callers do not want
// the release of AltGr to release LControl unless they specifically asked for LControl to be
// released too. This is because the caller may need LControl down to manifest something
// like ^c. So don't do: aModifiersLRnew &= ~MOD_LCONTROL.
// Without this fix, a hotkey like <^>!m::Send ^c would send "c" vs. "^c" on the German layout.
// See similar section below for more details.
aModifiersLRnow &= ~MOD_LCONTROL; // To reflect what KeyEvent(KEYUP, VK_RMENU) below will do.
}
else // No AltGr, so check if disguise is necessary (AltGr itself never needs disguise).
if (ctrl_not_down && aDisguiseUpWinAlt)
KeyEvent(KEYDOWNANDUP, VK_CONTROL, 0, NULL, false, aExtraInfo); // Disguise key release to suppress menu activation.
if (release_ralt && sTargetLayoutHasAltGr != CONDITION_TRUE) // If AltGr is present, RAlt would already have been released earlier since defer_alt_release would have been ignored for it.
// When calling KeyEvent(), probably best not to specify a scan code unless
// absolutely necessary, since some keyboards may have non-standard scan codes
// which KeyEvent() will resolve into the proper vk tranlations for us.
// Decided not to Sleep() between keystrokes, even zero, out of concern that this
// would result in a significant delay (perhaps more than 10ms) while the system
// is under load.
// Since the above didn't return early, keybd_event() has been used to change the state
// of at least one modifier. As a result, if the caller gave a non-NULL aTargetWindow,
// it wants us to check if that window belongs to our thread. If it does, we should do
// a short msg queue check to prevent an apparent synchronization problem when using
// ControlSend against the script's own GUI or other windows. Here is an example of a
// line whose modifier would not be in effect in time for its keystroke to be modified
// by it:
// ControlSend, Edit1, ^{end}, Test Window
// Update: Another bug-fix for v1.0.21, as was the above: If the keyboard hook is installed,
// the modifier keystrokes must have a way to get routed through the hook BEFORE the
// keystrokes get sent via PostMessage(). If not, the correct modifier state will usually
// not be in effect (or at least not be in sync) for the keys sent via PostMessage() afterward.
// Notes about the macro below:
// aTargetWindow!=NULL means ControlSend mode is in effect.
// The g_KeybdHook check must come first (it should take precedence if both conditions are true).
// -1 has been verified to be insufficient, at least for the very first letter sent if it is
// supposed to be capitalized.
// g_MainThreadID is the only thread of our process that owns any windows.
int press_duration = (sSendMode == SM_PLAY) ? g.PressDurationPlay : g.PressDuration;
if (press_duration > -1) // SM_PLAY does use DoKeyDelay() to store a delay item in the event array.
// Since modifiers were changed by the above, do a key-delay if the special intra-keystroke
// delay is in effect.
// Since there normally isn't a delay between a change in modifiers and the first keystroke,
// if a PressDuration is in effect, also do it here to improve reliability (I have observed
// cases where modifiers need to be left alone for a short time in order for the keystrokes
// that follow to be be modified by the intended set of modifiers).
DoKeyDelay(press_duration); // It knows not to do the delay for SM_INPUT.
else // Since no key-delay was done, check if a a delay is needed for any other reason.
{
// IMPORTANT UPDATE for v1.0.39: Now that the hooks are in a separate thread from the part
// of the program that sends keystrokes for the script, you might think synchronization of
// keystrokes would become problematic or at least change. However, this is apparently not
// the case. MSDN doesn't spell this out, but testing shows that what happens with a low-level
// hook is that the moment a keystroke comes into a thread (either physical or simulated), the OS
// immediately calls something similar to SendMessage() from that thread to notify the hook
// thread that a keystroke has arrived. However, if the hook thread's priority is lower than
// some other thread next in line for a timeslice, it might take some time for the hook thread
// to get a timeslice (that's why the hook thread is given a high priority).
// The SendMessage() call doesn't return until its timeout expires (as set in the registry for
// hooks) or the hook thread processes the keystroke (which requires that it call something like
// GetMessage/PeekMessage followed by a HookProc "return"). This is good news because it serializes
// keyboard and mouse input to make the presence of the hook transparent to other threads (unless
// the hook does something to reveal itself, such as suppressing keystrokes). Serialization avoids
// any chance of synchronization problems such as a program that changes the state of a key then
// immediately checks the state of that same key via GetAsyncKeyState(). Another way to look at
// all of this is that in essense, a single-threaded hook program that simulates keystrokes or
// mouse clicks should behave the same when the hook is moved into a separate thread because from
// the program's point-of-view, keystrokes & mouse clicks result in a calling the hook almost
// exactly as if the hook were in the same thread.
if (aTargetWindow)
{
if (g_KeybdHook)
SLEEP_WITHOUT_INTERRUPTION(0) // Don't use ternary operator to combine this with next due to "else if".
else if (GetWindowThreadProcessId(aTargetWindow, NULL) == g_MainThreadID)
SLEEP_WITHOUT_INTERRUPTION(-1)
}
}
// Commented out because a return value is no longer needed by callers (since we do the key-delay here,
// if appropriate).
//return aModifiersLRnow ^ aModifiersLRnew; // Calculate the set of modifiers that changed (currently excludes AltGr's change of LControl's state).
// NOTES about "release_shift_before_alt_ctrl":
// If going down on alt or control (but not both, though it might not matter), and shift is to be released:
// Release shift first.
// If going down on shift, and control or alt (but not both) is to be released:
// Release ctrl/alt first (this is already the case so nothing needs to be done).
//
// Release both shifts before going down on lalt/ralt or lctrl/rctrl (but not necessary if going down on
// *both* alt+ctrl?
// Release alt and both controls before going down on lshift/rshift.
// Rather than the below, do the above (for the reason below).
// But if do this, don't want to prevent a legit/intentional language switch such as:
// Send {LAlt down}{Shift}{LAlt up}.
// If both Alt and Shift are down, Win or Ctrl (or any other key for that matter) must be pressed before either
// is released.
// If both Ctrl and Shift are down, Win or Alt (or any other key) must be pressed before either is released.
// remind: Despite what the Regional Settings window says, RAlt+Shift (and Shift+RAlt) is also a language hotkey (i.e. not just LAlt), at least when RAlt isn't AltGr!
// remind: Control being down suppresses language switch only once. After that, control being down doesn't help if lalt is re-pressed prior to re-pressing shift.
//
// Language switch occurs when:
// alt+shift (upon release of shift)
// shift+alt (upon release of lalt)
// ctrl+shift (upon release of shift)
// shift+ctrl (upon release of ctrl)
// Because language hotkey only takes effect upon release of Shift, it can be disguised via a Control keystroke if that is ever needed.
// NOTES: More details about disguising ALT and WIN:
// Registered Alt hotkeys don't quite work if the Alt key is released prior to the suffix.
// Key history for Alt-B hotkey released this way, which undesirably activates the menu bar:
// A4 038 d 0.03 Alt
// 42 030 d 0.03 B
// A4 038 u 0.24 Alt
// 42 030 u 0.19 B
// Testing shows that the above does not happen for a normal (non-hotkey) alt keystroke such as Alt-8,
// so the above behavior is probably caused by the fact that B-down is suppressed by the OS's hotkey
// routine, but not B-up.
// The above also happens with registered WIN hotkeys, but only if the Send cmd resulted in the WIN
// modifier being pushed back down afterward to match the fact that the user is still holding it down.
// This behavior applies to ALT hotkeys also.
// One solution: if the hook is installed, have it keep track of when the start menu or menu bar
// *would* be activated. These tracking vars can be consulted by the Send command, and the hook
// can also be told when to use them after a registered hotkey has been pressed, so that the Alt-up
// or Win-up keystroke that belongs to it can be disguised.
// The following are important ways in which other methods of disguise might not be sufficient:
// Sequence: shift-down win-down shift-up win-up: invokes Start Menu when WIN is held down long enough
// to auto-repeat. Same when Ctrl or Alt is used in lieu of Shift.
// Sequence: shift-down alt-down alt-up shift-up: invokes menu bar. However, as long as another key,
// even Shift, is pressed down *after* alt is pressed down, menu bar is not activated, e.g. alt-down
// shift-down shift-up alt-up. In addition, CTRL always prevents ALT from activating the menu bar,
// even with the following sequences:
// ctrl-down alt-down alt-up ctrl-up
// alt-down ctrl-down ctrl-up alt-up
// (also seems true for all other permutations of Ctrl/Alt)
if (aModifiersLR & MOD_LWIN) strcat(aBuf, "LWin ");
if (aModifiersLR & MOD_RWIN) strcat(aBuf, "RWin ");
if (aModifiersLR & MOD_LSHIFT) strcat(aBuf, "LShift ");
if (aModifiersLR & MOD_RSHIFT) strcat(aBuf, "RShift ");
if (aModifiersLR & MOD_LCONTROL) strcat(aBuf, "LCtrl ");
if (aModifiersLR & MOD_RCONTROL) strcat(aBuf, "RCtrl ");
if (aModifiersLR & MOD_LALT) strcat(aBuf, "LAlt ");
if (aModifiersLR & MOD_RALT) strcat(aBuf, "RAlt ");
return aBuf;
}
bool ActiveWindowLayoutHasAltGr()
// Thread-safety: See comments in LayoutHasAltGr() below.
{
Get_active_window_keybd_layout // Defines the variable active_window_keybd_layout for use below.
return LayoutHasAltGr(active_window_keybd_layout) == CONDITION_TRUE; // i.e caller wants both CONDITION_FALSE and LAYOUT_UNDETERMINED to be considered non-AltGr.
// Thread-safety: While not thoroughly thread-safe, due to the extreme simplicity of the cache array, even if
// a collision occurs it should be inconsequential.
// Caller must ensure that aLayout is a valid layout (special values like 0 aren't supported here).
// If aHasAltGr is not at its default of LAYOUT_UNDETERMINED, the specified layout's has_altgr property is
// updated to the new value, but only if it is currently undetermined (callers can rely on this).
{
// Layouts are cached for performance (to avoid the discovery loop later below).
int i;
for (i = 0; i < MAX_CACHED_LAYOUTS && sCachedLayout[i].hkl; ++i)
if (sCachedLayout[i].hkl == aLayout) // Match Found.
{
if (aHasAltGr != LAYOUT_UNDETERMINED && sCachedLayout[i].has_altgr == LAYOUT_UNDETERMINED) // Caller relies on this.
sCachedLayout[i].has_altgr = aHasAltGr;
return sCachedLayout[i].has_altgr;
}
// Since above didn't return, this layout isn't cached yet. So create a new cache entry for it and
// determine whether this layout has an AltGr key. If i<MAX_CACHED_LAYOUTS (which it almost always will be),
// there's room in the array for a new cache entry. In the very unlikely event that there isn't room,
// overwrite an arbitrary item in the array. An LRU/MRU algorithm (timestamp) isn't used because running out
// of slots seems too unlikely, and the consequences of running out are merely a slight degradation in performance.
CachedLayoutType &cl = sCachedLayout[(i < MAX_CACHED_LAYOUTS) ? i : MAX_CACHED_LAYOUTS-1];
if (aHasAltGr != LAYOUT_UNDETERMINED) // Caller determined it for us. See top of function for explanation.
{
cl.hkl = aLayout;
return cl.has_altgr = aHasAltGr;
}
// Otherwise, do AltGr detection on this newly cached layout so that we can return the AltGr state to caller.
// This detection is probably not 100% reliable because there may be some layouts (especially custom ones)
// that have an AltGr key yet none of its characters actually require AltGr to manifest. A more reliable
// way to detect AltGr would be to simulate an RALT keystroke (maybe only an up event, not a down) and have
// a keyboard hook catch and block it. If the layout has altgr, the hook would see a driver-generated LCtrl
// keystroke immediately prior to RAlt.
// Performance: This loop is quite fast. Doing this section 1000 times only takes about 160ms
// on a 2gHz system (0.16ms per call).
SHORT s; // Also, an int is used for "i" vs. char to avoid overflow on final character.
for (cl.has_altgr = LAYOUT_UNDETERMINED, i = 32; i < 256; ++i) // Include Spacebar up through final ANSI character (i.e. include 255 but not 256).
{
s = VkKeyScanEx((char)i, aLayout);
// Check for presence of Ctrl+Alt but allow other modifiers like Shift to be present because
// I believe there are some layouts that manifest characters via Shift+AltGr.
if (s != -1 && (s & 0x600) == 0x600) // In this context, Ctrl+Alt means AltGr.
{
cl.has_altgr = CONDITION_TRUE;
break;
}
}
// If loop didn't break, leave cl.has_altgr as LAYOUT_UNDETERMINED because we can't be sure whether AltGr is
// present (see other comments for details).
cl.hkl = aLayout; // This is done here (immediately after has_altgr was set in the loop above) rather than earlier to minimize the consequences of not being fully thread-safe.
return cl.has_altgr;
}
char *SCtoKeyName(sc_type aSC, char *aBuf, int aBufSize)
// aBufSize is an int so that any negative values passed in from caller are not lost.
// Always produces a non-empty string.
{
for (int i = 0; i < g_key_to_sc_count; ++i)
{
if (g_key_to_sc[i].sc == aSC)
{
strlcpy(aBuf, g_key_to_sc[i].key_name, aBufSize);
return aBuf;
}
}
// Since above didn't return, no match was found. Use the default format for an unknown scan code:
snprintf(aBuf, aBufSize, "SC%03x", aSC);
return aBuf;
}
char *VKtoKeyName(vk_type aVK, sc_type aSC, char *aBuf, int aBufSize)
// aBufSize is an int so that any negative values passed in from caller are not lost.
// Caller may omit aSC and it will be derived if needed.
{
for (int i = 0; i < g_key_to_vk_count; ++i)
{
if (g_key_to_vk[i].vk == aVK)
{
strlcpy(aBuf, g_key_to_vk[i].key_name, aBufSize);
return aBuf;
}
}
// Since above didn't return, no match was found. Ask the OS for the name instead (it's probably
// a letter key such as A through Z, but could be anything for which we don't have a listing):
return GetKeyName(aVK, aSC, aBuf, aBufSize);
}
sc_type TextToSC(char *aText)
{
if (!*aText) return 0;
for (int i = 0; i < g_key_to_sc_count; ++i)
if (!stricmp(g_key_to_sc[i].key_name, aText))
return g_key_to_sc[i].sc;
// Do this only after the above, in case any valid key names ever start with SC:
if (toupper(*aText) == 'S' && toupper(*(aText + 1)) == 'C')
return (sc_type)strtol(aText + 2, NULL, 16); // Convert from hex.
// If non-NULL, pModifiersLR contains the initial set of modifiers provided by the caller, to which
// we add any extra modifiers required to realize aChar.
{
// For v1.0.25.12, it seems best to avoid the many recent problems with linefeed (`n) being sent
// as Ctrl+Enter by changing it to always send a plain Enter, just like carriage return (`r).
if (aChar == '\n')
return VK_RETURN;
// Otherwise:
SHORT mod_plus_vk = VkKeyScanEx(aChar, aKeybdLayout); // v1.0.44.03: Benchmark shows that VkKeyScanEx() is the same speed as VkKeyScan() when the layout has been pre-fetched.
vk_type vk = LOBYTE(mod_plus_vk);
char keyscan_modifiers = HIBYTE(mod_plus_vk);
if (keyscan_modifiers == -1 && vk == (UCHAR)-1) // No translation could be made.
return 0;
// For v1.0.35, pModifiersLR was changed to modLR vs. mod so that AltGr keys such as backslash and
// '{' are supported on layouts such as German when sending to apps such as Putty that are fussy about
// which ALT key is held down to produce the character. The following section detects AltGr by the
// assuming that any character that requires both CTRL and ALT (with optional SHIFT) to be held
// down is in fact an AltGr key (I don't think there are any that aren't AltGr in this case, but
// confirmation would be nice). Also, this is not done for Win9x because the distinction between
// right and left-alt is not well-supported and it might do more harm than good (testing is
// needed on fussy apps like Putty on Win9x). UPDATE: Windows NT4 is now excluded from this
// change because apparently it wants the left Alt key's virtual key and not the right's (though
// perhaps it would prefer the right scan code vs. the left in apps such as Putty, but until that
// is proven, the complexity is not added here). Otherwise, on French and other layouts on NT4,
// AltGr-produced characters such as backslash do not get sent properly. In hindsight, this is
// not suprising because the keyboard hook also receives neutral modifier keys on NT4 rather than
// a more specific left/right key.
// The win docs for VkKeyScan() are a bit confusing, referring to flag "bits" when it should really
// say flag "values". In addition, it seems that these flag values are incompatible with
// MOD_ALT, MOD_SHIFT, and MOD_CONTROL, so they must be translated:
if (pModifiersLR) // The caller wants this info added to the output param.
{
// Best not to reset this value because some callers want to retain what was in it before,
// merely merging these new values into it:
//*pModifiers = 0;
if ((keyscan_modifiers & 0x06) == 0x06 && g_os.IsWin2000orLater()) // 0x06 means "requires/includes AltGr".
{
// v1.0.35: The critical difference below is right vs. left ALT. Must not include MOD_LCONTROL
// because simulating the RAlt keystroke on these keyboard layouts will automatically
// press LControl down.
*pModifiersLR |= MOD_RALT;
}
else // Do normal/default translation.
{
// v1.0.40: If caller-supplied modifiers already include the right-side key, no need to
// add the left-side key (avoids unnecessary keystrokes).
if ( (keyscan_modifiers & 0x02) && !(*pModifiersLR & (MOD_LCONTROL|MOD_RCONTROL)) )
*pModifiersLR |= MOD_LCONTROL; // Must not be done if requires_altgr==true, see above.
if ( (keyscan_modifiers & 0x04) && !(*pModifiersLR & (MOD_LALT|MOD_RALT)) )
*pModifiersLR |= MOD_LALT;
}
// v1.0.36.06: Done unconditionally because presence of AltGr should not preclude the presence of Shift.
// v1.0.40: If caller-supplied modifiers already contains MOD_RSHIFT, no need to add LSHIFT (avoids
// unnecessary keystrokes).
if ( (keyscan_modifiers & 0x01) && !(*pModifiersLR & (MOD_LSHIFT|MOD_RSHIFT)) )